﻿#region

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;

#endregion

namespace XXS.Common.Paginate
{
    public static class StaticPaginateHelper
    {
        public static IEnumerable<object> ToPaginateData(this IOrderedQueryable<object> source, PaginateModel im)
        {
            return new Paginate(source, im).PageData;
        }

        public static Paginate<TR> ToPaginate<TR>(this IOrderedQueryable<object> source, PaginateModel im)
            where TR : class, new()
        {
            return new Paginate<TR>(source, im);
        }

        public static IEnumerable<TR> ToPaginateData<T, TR>(this IOrderedQueryable<T> source, PaginateModel im,
            Func<T, TR> conventer) where T : class where TR : new()
        {
            return new Paginate<T, TR>(source, im, conventer).PageData;
        }

        public static Paginate<T, TR> ToPaginate<T, TR>(this IOrderedQueryable<T> source, PaginateModel im,
            Func<T, TR> conventer) where T : class where TR : new()
        {
            return new Paginate<T, TR>(source, im, conventer);
        }
    }

    public class Paginate : PaginateBase, IPaginate<object>
    {
        private readonly IQueryable<Object> _pagedData;
        private IQueryable<Object> _source;
        private int? _sourceCount;

        #region MVC Paginate

        public Paginate(IQueryable<Object> data, PaginateModel im, ControllerContext cc)
            : this(data, im.Page, im.PageSize, cc)
        {
        }


        /// <summary>
        ///     This Paginate reload is for MVC
        /// </summary>
        /// <param name="data"></param>
        /// <param name="page"></param>
        /// <param name="pagesize"></param>
        /// <param name="cc"></param>
        public Paginate(IQueryable<Object> data, int page, int pagesize, ControllerContext cc)
            : base(pagesize, cc)
        {
            if (data == null)
                throw new ArgumentNullException(nameof(data));

            Source = data;
            TotalPage = (int)Math.Ceiling(SourceCount / (decimal)PageSize);
            CurrentPage = page;
            PageType = ShowType.MVC;
            _pagedData = _source.Skip((CurrentPage - 1) * PageSize).Take(PageSize);
        }

        /// <summary>
        ///     This Paginate reload is for MVC
        /// </summary>
        /// <param name="data"></param>
        /// <param name="page"></param>
        /// <param name="pagesize"></param>
        /// <param name="cc"></param>
        /// <param name="rootValue"></param>
        public Paginate(IQueryable<Object> data, int page, int pagesize, ControllerContext cc,
            IDictionary<string, object> rootValue)
            : this(data, page, pagesize, cc)
        {
            SetRootValue(rootValue);
        }

        #endregion

        #region ExtJS Paginate

        /// <summary>
        ///     This Paginate reload Init is for ExtJS
        /// </summary>
        /// <param name="data">Data Source</param>
        /// <param name="page"></param>
        /// <param name="limit">Request number</param>
        public Paginate(IQueryable<Object> data, int page, int limit)
            : base(limit)
        {
            if (data == null)
                throw new ArgumentNullException(nameof(data));

            Source = data;
            TotalPage = (int)Math.Ceiling(SourceCount / (decimal)PageSize);
            CurrentPage = page;
            PageType = ShowType.ExtJs;
            _pagedData = _source.Skip((CurrentPage - 1) * PageSize).Take(PageSize);
        }

        public Paginate(IQueryable<Object> data, PaginateModel im)
            : this(data, im.Page, im.Limit)
        {
        }

        #endregion

        public IQueryable<object> Source
        {
            get { return _source; }
            private set { _source = value; }
        }

        public virtual IEnumerable<object> PageData => _pagedData.ToList();


        public object Model => new object();

        public sealed override int SourceCount
        {
            get
            {
                if (!_sourceCount.HasValue)
                    _sourceCount = _source.Count();

                return _sourceCount.Value;
            }
        }

        public sealed override int TotalPage
        {
            get { return _totalPage; }
            protected set
            {
                if (value > 0)
                    _totalPage = value;
            }
        }

        public IEnumerable<object> Data => Source;
    }

    public class Paginate<TR> : Paginate, IPaginate<TR> where TR : class, new()
    {
        private readonly Func<object, TR> _conventer;

        public Paginate(IQueryable<object> data, PaginateModel im, Func<object, TR> conventer)
            : base(data, im)
        {
            _conventer = conventer;
        }

        public Paginate(IQueryable<object> data, PaginateModel im)
            : this(data, im, o =>
            {
                var tr = o as TR;

                if (tr != null)
                    return tr;

                var mtr = new TR() as IMapFrom<TR>;

                if (mtr != null)
                    return mtr.MapFrom(o);

                throw new Exception($"分页数据操作中断,无法将数据类型:{data.GetType().GetGenericArguments().First().FullName}转换为预期类型:{typeof(TR).FullName},请在{typeof(TR).FullName}实现IMapping<{typeof(TR).FullName}>!");
            })
        {
        }

        public new TR Model => new TR();

        public new IEnumerable<TR> Data => Source.Select(_conventer);

        public new IEnumerable<TR> PageData => base.PageData.ToList().Select(_conventer);
    }

    public class Paginate<T, TR> : Paginate, IPaginate<TR> where T : class where TR : new()
    {
        private readonly Func<T, TR> _conventer;

        public Paginate(IQueryable<T> data, PaginateModel im, Func<T, TR> conventer)
            : base(data, im)
        {
            _conventer = conventer;
        }

        public new TR Model => new TR();

        public new IEnumerable<TR> Data => Source.ToList().Cast<T>().Select(_conventer);

        public new IEnumerable<TR> PageData => base.PageData.ToList().Cast<T>().Select(_conventer);
    }
}